﻿using System;   
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

using Alicanto.Helpers;
using Alicanto.Execution;
using Alicanto.Lexing;

namespace Alicanto.Compilation.Code.Blocks
{
    internal class Function : ICodeGenerator, IRequiresDelayedParsing
    {
        public Expression Code { get; private set; }

        public Function()
        {
            this.Code = Expression.Empty();
        }

        public IEnumerable<ParseInstruction> Generate(TokenSequence tokens, Scope scope)
        {
            EnterScope enter = new EnterScope();
            yield return enter;

            // Function identifier
            string function = tokens.Pop(TokenType.SimpleName).Str;

            // Function return type (defaults to object)
            Type returns = typeof(object);

            if (tokens.Peek() == Token.Colon &&
                (tokens.Peek(Token.Colon) == Token.BracketOpen || tokens.Peek(Token.Colon).Type == TokenType.SimpleName)
            )
            {
                //return type is defined
                tokens.Pop();
                returns = Typing.FromString(tokens.Pop(TokenType.SimpleName).Str);
            }

            // Function arguments
            IList<Expression> arguments = new List<Expression>();
            if (tokens.Peek() == Token.BracketOpen)
            {
                enter.Scope.BeginArgumentSequence();
                ParseSequence sequence = new ParseSequence(Token.BracketOpen, Token.BracketClose);
                yield return sequence;
                enter.Scope.EndArgumentSequence();
                arguments = sequence.Expressions;
            }

            // Define return label
            enter.Scope.RegisterLabel("return", Expression.Label(returns));

            // Function body
            Parse body = new Parse();
            yield return body;

            yield return new LeaveScope();

            // Resulting function
            List<Expression> statements = new List<Expression>();
            statements.AddRange(body.Expressions);
            statements.Add(
                Expression.Label(
                    enter.Scope.ResolveLabel("return"),
                    Expression.Default(returns)
                )
            );

            LambdaExpression lambda = Expression.Lambda(
                Expression.Block(
                    statements
                ),
                function,
                enter.Scope.RequiredArguments
            );

            ExecutionContextMember member = new ExecutionContextMember(lambda, enter.Scope);
            scope.ExecutionContext[function] = member;
        }

        public TokenSequence SkipTokens(TokenSequence tokens)
        {
            TokenSequence skipped = new TokenSequence();

            int open = 1;
            while (tokens.Available > 0 && open > 0)
            {
                Token token = tokens.Pop();
                skipped.Push(token);

                if (token.Type == TokenType.Block)
                    open++;
                else if (token.Type == TokenType.End)
                    open--;
            }

            if (open > 0)
                throw new Exception();

            return skipped;
        }
    }
}
